﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Objects;
using System.IO;
using System.Linq;
//using WcfService1;
using System.Threading;
using System.Xml.Serialization;
using GameObjects;
using System.Xml;

namespace ServerObjects
{
    public class GameServer
    {
        //private GameState gameState;
        readonly Entities objectContext;

        public GameServer()
        {
            objectContext = new Entities();
        }

        public GameState GetGameState(int gameId)
        {
            var game = GetGame(gameId);

            var gameState = new GameState
            {
                Turn = 0,
                Name = game.Name,
                //TurnTimeLimit = GameState.ParseTurnTimeLimit(game.GameTurnTimeLimit),
            };
            //gameState.MapView.l
            gameState.MapView = new MapView(game.Map.Name, game.Map.TerrainSet.TerrainXml, game.Map.Hexes);

            //LoadMap(gameState, game.Map);


            return gameState;
        }

        //public byte[] GetCompressedGameState(int gameId)
        //{
        //    //if (_objectContext.GameTurn.Count(o => o.Game.Id == gameId) == 0)
        //    //    return GetGameState(gameId, 0);

        //    var gt = GetCurrentGameTurn(gameId);
        //    return gt == null ? null : GetCompressedGameState(gameId, gt.Turn);
        //}

        //public byte[] GetCompressedGameState(int gameId, int turn)
        //{
        //    var gt = GetGameTurn(gameId, turn);
            
        //    return gt == null ? null : gt.GameState;
        //}

        public GameTurn GetCurrentGameTurn(int gameId)
        {
            var gt = objectContext.GameTurn.Where(o => o.Game.Id == gameId).OrderByDescending(o => o.Turn).FirstOrDefault();
            if (gt == null)
            {
                //DeleteGame(gameId);
                return null;
            }
            objectContext.Refresh(RefreshMode.StoreWins, gt);

            return gt;
        }

        private void DeleteGame(int gameId)
        {
            try
            {
                foreach (var gt in objectContext.GameTurn.Where(o => o.Game.Id == gameId))
                {
                    objectContext.DeleteObject(gt);
                }
                objectContext.DeleteObject(GetGame(gameId));
                objectContext.SaveChanges();
            }
            catch (Exception)
            {
                throw;
            }
        }

        private GameTurn GetGameTurn(int gameId, int turn)
        {
            var gameTurns = objectContext.GameTurn.Where(o => o.Game.Id == gameId && o.Turn == turn);
            
            if (gameTurns.Count() > 1)
                throw new Exception(gameTurns.Count() + " copies of a game " + gameId + ", turn " + turn + " exist. This has corrupted the database.");

            objectContext.Refresh(RefreshMode.StoreWins, gameTurns);

            return gameTurns.FirstOrDefault();
        }

        public bool GameTurnExists(int gameId, int turn)
        {
            //EndTurn(gameId, turn - 1);

            return GetGameTurn(gameId, turn) != null;
        }

        public int CreateGame(int mapId, string gameName, int turnLimit, TimeSpan timeLimit)
        {
            var map = objectContext.Map.Include("TerrainSet").First(o => o.Id == mapId);
            var game = new Game { Name = gameName, Map = map, TurnLimit = turnLimit, TurnTimeLimit = timeLimit == TimeSpan.Zero ? "" : timeLimit.ToString() };
            //CreateGameTurn(game.Id, 0);

            var gameTurn = new GameTurn
                               {
                                   Game = game,
                                   Turn = 0,
                                   Orders = Utils.SerialiseHelper.SerialiseAndCompress(new List<UnitOrderDto>()),
                                   TurnEnds = CalculateEndOfTurn(game)
                               };

            objectContext.SaveChanges();

            return game.Id;
        }

        

        private DateTime CalculateEndOfTurn(int gameId)
        {
            var game = GetGame(gameId);
            
            if (game == null)
                throw new Exception();

            return CalculateEndOfTurn(game);
        }

        private static DateTime CalculateEndOfTurn(Game game)
        {
            var timeLimit = GameState.ParseTurnTimeLimit(game.TurnTimeLimit);
            return DateTime.Now.Add(timeLimit);
        }

        private List<Exception> CreateGameTurn(int gameId, int turn)
        {
            var exceptionLog = new List<Exception>();
            var success = false;
            var attempts = 0;
            while (!success && attempts < 5)
            {
                GameTurn gameTurn = null;
                try
                {
                    gameTurn = GetGameTurn(gameId, turn) ??
                             new GameTurn
                                 {
                                     Game = objectContext.Game.First(o => o.Id == gameId),
                                     Turn = turn,
                                 };

                    gameTurn.Orders = CompressHelper.Compress(Utils.SerialiseHelper.SerializeToBase64(new List<UnitOrderDto>()));
                    gameTurn.TurnEnds = CalculateEndOfTurn(gameId);

                    objectContext.SaveChanges();

                    success = true;
                }
                catch (OptimisticConcurrencyException ex)
                {
                    exceptionLog.Add(ex);
                }
                catch (UpdateException ex)
                {
                    if (ex.InnerException.Message.Contains("Cannot insert duplicate key in object"))
                    {
                        objectContext.DeleteObject(gameTurn);
                    }
                    exceptionLog.Add(ex);
                }
                attempts++;
                Thread.Sleep(1000);
            }
            return exceptionLog;
        }
        
        public Game GetGame(int gameId)
        {
            return objectContext.Game.Include("Map.TerrainSet").FirstOrDefault(o => o.Id == gameId);
        }

        public List<Faction> GetFactions(int gameId)
        {
            var gameState = GetGameState(gameId);
            if (gameState == null)
                return null;

            //var gameState = (GameState)Utils.SerialiseHelper.DeserializeToObject(CompressHelper.Decompress(compressedGameState));
            return gameState.MapView.Factions;
        }

        public bool SaveOrders(int gameId, int turn, int factionId, List<UnitOrderDto> newOrders)
        {

            var gameTurn = GetCurrentGameTurn(gameId);

            if (gameTurn.Turn != turn)
                return true;

            var orders = (List<UnitOrderDto>)Utils.SerialiseHelper.DeserialiseAndUncompress(gameTurn.Orders);

            foreach (var order in newOrders)
            {
                var oldOrder = orders.FirstOrDefault(o => o.UnitId == order.UnitId);

                if (oldOrder == null)
                    orders.Add(order);
                else
                    orders[orders.IndexOf(oldOrder)] = order;
            }
                
            gameTurn.Orders = Utils.SerialiseHelper.SerialiseAndCompress(orders);
            objectContext.SaveChanges();

            return true;
        }

        //public bool SaveOrders(int gameId, int turn, List<Faction> factionOrders)
        //{
        //    SaveGameTurn(gameId, turn, factionOrders);
        //    return true;
        //}

        public void EndTurn(int gameId, int turn)
        {
            var gameTurn = GetCurrentGameTurn(gameId);

            // Only end the turn of the most recent turn
            if (gameTurn == null)
                throw new NullReferenceException("There is no game turn for game " + gameId + " turn " + turn + ". Most likely the previous turn has not ended yet.");
            if (gameTurn.Turn != turn)
                throw new ApplicationException("Game " + gameId + " turn " + turn + " is not the current game turn. Current game turn is " + gameTurn.Turn);

            //var orders = GetOrders(gameId, turn);

            //// Check to see if game is already finished
            //if (gameState.GameOver)
            //{
            //    SaveGame(gameId, true);
            //    throw new ApplicationException("Game " + gameId + " has already finished.");
            //}  

            // Check to see if turn time limit has expired and/or if all orders are in
            if (GameState.GameTurnTimeExpired(DateTime.Now, gameTurn.TurnEnds))
                //if (!gameState.AllOrdersIn)
                //{
                //    throw new ApplicationException("Game " + gameId + " turn " + turn +
                //                                   " is still waiting on orders from " + gameState.WaitingOn + ".");
                //}

                CreateGameTurn(gameId, turn + 1);
        }

        public byte[] GetOrdersAsBinary(int gameId, int turn)
        {
            var gt = GetGameTurn(gameId, turn);
            if (gt == null)
                throw new NullReferenceException("Game " + gameId + " turn " + turn + " does not exist.");

            return  gt.Orders;
        }

        public List<UnitOrderDto> GetOrders(int gameId, int turn)
        {
            return (List<UnitOrderDto>)Utils.SerialiseHelper.DeserialiseAndUncompress(GetOrdersAsBinary(gameId, turn));
        }

        private void SaveGame(int gameId, bool gameOver)
        {
            var game = GetGame(gameId);
            if (game == null || game.GameOver)
                return;

            game.GameOver = gameOver;
            objectContext.SaveChanges();
        }

        public IEnumerable<Map> GetMaps()
        {
            return objectContext.Map;
        }

        public void SaveMap(int id)
        {
            var map = objectContext.Map.First(o => o.Id == id);

            var gameState = GetGameState(1);

           
                var xmlSerializer = new XmlSerializer(typeof(List<Hex>));
                var sw = new StringWriter();
                xmlSerializer.Serialize(sw, gameState.MapView.Board.Hexes);

                map.Units = sw.ToString();

                objectContext.SaveChanges();
           
        }

        //public List<FactionDto> GetFactionsByMap(int mapId)
        //{
        //    var map = _objectContext.Map.First(o => o.Id == mapId);
        //    return GetFactionDtos(GameState.BuildFactions(map.Factions));
        //}

        public Unit GetUnit(int unitId)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<Game> GetGames(bool all)
        {
            return objectContext.Game.Where(o => all || !o.GameOver);
        }

        public bool DeleteOrder(int gameId, int turn, int unitId)
        {
            var gameTurn = GetCurrentGameTurn(gameId);

            if (gameTurn.Turn != turn)
                return true;

            var orders = (List<UnitOrderDto>)Utils.SerialiseHelper.DeserialiseAndUncompress(gameTurn.Orders);

            orders.RemoveAll(o => o.UnitId == unitId);

            gameTurn.Orders = Utils.SerialiseHelper.SerialiseAndCompress(orders);
            objectContext.SaveChanges();

            return true;
        }

        public void EndGame(int gameId)
        {
            var game = GetGame(gameId);
            game.GameOver = true;
            objectContext.SaveChanges();
        }
    }
}